
#include <limits>
#include "gui_graph.h"
#include "graphics_functions.h"
#include <sstream>

#undef max
#undef min

using namespace WtfGui;
using namespace WtfEngine;


/**** Axes ****/

Axes::GraphEntry::GraphEntry(const IGraphData::tRef& data)
: rData(data), vColour(0, 0, 1.0, 1.0) {
};
Axes::GraphEntry::GraphEntry(const IGraphData::tRef& data, const fVector4D& colour)
: rData(data), vColour(colour) {
};


Axes::Axes(const WtfEngine::String& sXLabel, const WtfEngine::String& sYLabel)
: Widgets::Image(FBOTexture::tCacheRef()), msXLabel(sXLabel), msYLabel(sYLabel),
mrFont("v_gui_font"), mrValueFormatter(new ObjFunctor<Axes, String, float>(this, &Axes::FormatValue)) {
	Update();
	//getLayout()->AddObserver(new ObjFunctor<Axes>(this, &Axes::Update));
};

void Axes::AddGraph(const GraphEntry& graph) {
	GraphEntry ge(graph);
	ge.rObserver = new ObjFunctor<Axes>(this, &Axes::Update);
	ge.rData->AddObserver(ge.rObserver);

	mvGraphs.push_back(ge);
	Update();
};

void Axes::RemoveGraph(const IGraphData::tRef& rGraph) {
	for(std::list<GraphEntry>::const_iterator i = mvGraphs.begin(); i != mvGraphs.end(); i++) {
		if(*i == rGraph) {
			rGraph->RemoveObserver(i->rObserver);
		};
	};
	mvGraphs.remove(rGraph);
	Update();
};

void Axes::Destroy() {
	for(std::list<GraphEntry>::const_iterator i = mvGraphs.begin(); i != mvGraphs.end(); i++) {
		i->rData->RemoveObserver(i->rObserver);
	};
	mvGraphs.clear();
};

void Axes::Update() {
	std::list<GraphEntry>::iterator iter;
	int iNumXValues = 0;
	float fColWidth, fMin = std::numeric_limits<float>::max(), fMax = std::numeric_limits<float>::min();
	iVector2D vSize = getInnerSize();

	int iYLabelWidth = 20, iYValuesWidth = 20, iXLabelHeight = 20, iXValuesHeight = 20,
		iYLength = vSize[1] - (iXLabelHeight + iXValuesHeight),
		iXLength = vSize[0] - (iYLabelWidth + iYValuesWidth);

	if(iYLength <= 0 || iXLength <= 0)
		return;


	//
	// Update graph data
	//

	for(iter = mvGraphs.begin(); iter != mvGraphs.end(); iter++) {
		iNumXValues = maxf(iNumXValues, iter->rData->getNumValues());
	};
	mvXValues.resize(iNumXValues);

	for(iter = mvGraphs.begin(); iter != mvGraphs.end(); iter++) {
		int c = iter->rData->getNumValues();
		if(c == iNumXValues) {
			for(int i = 0; i < iNumXValues; i++) {
				mvXValues[i].sLabel = iter->rData->getLabel(i);
				mvXValues[i].iColumnWidth = iXLength / iNumXValues;
			};
		};

		iter->mvYValues.resize(c);
		for(int i = 0; i < c; i++) {
			iter->mvYValues[i] = iter->rData->getValue(i);
			fMin = minf(fMin, iter->mvYValues[i]);
			fMax = maxf(fMax, iter->mvYValues[i]);
		};
	};

	// Allocate new texture
	mrTexture = new FBOTexture();
	mrTexture->Create(vSize);
	setImage(mrTexture);

	//
	// Now we have the data, start drawing
	//

	mrTexture->Activate();
	glClearColor(1.0, 1.0, 1.0, 0.0); // XXX: OpenGL Specific
	WtfGraphics::SetupOrthoProjection(mrTexture);
	WtfGraphics::ClearScreen();
	WtfGraphics::Translate(tVector(0,mrTexture->getSize()[1], -0.1));

	// Axes
	WtfGraphics::SetColour(fVector4D(0.0, 0.0, 0.0, 1.0)); // Solid black
	WtfGraphics::Translate2D(iVector2D(iYLabelWidth + iYValuesWidth, 0));
	WtfGraphics::Draw2DLine(iVector2D(), iVector2D(0.0, -iYLength), 1);
	WtfGraphics::Draw2DLine(iVector2D(0, -iYLength), iVector2D(iXLength, -iYLength), 1);

	

	if(fMin > fMax) {
		// No Data
	} else {
		if(fMin == fMax) {
			// One item of data. Spread to give 1 unit range.
			fMin -= 0.5;
			fMax += 0.5;
		};

		float fScale = float(iYLength) / (fMax - fMin);

		// Grid
		WtfGraphics::SetColour(fVector4D(0, 0, 0, 0.5));
		for(int i = iXLength / (iNumXValues * 2); i < iXLength; i += iXLength / iNumXValues) {
			WtfGraphics::Draw2DLine(iVector2D(i, 0), iVector2D(i, -iYLength), 1);
			for(int j = 0; j < iYLength; j += 5) {
				WtfGraphics::Draw2DLine(iVector2D(0, -j), iVector2D(iXLength, -j), 1);
			};
		};

		// Labels
		WtfGraphics::SaveView();
		WtfGraphics::Translate2D(iVector2D(-iYValuesWidth, 0));
		for(int j = 0; j < iYLength; j += 30) {
			mrFont->Value->DrawText((*mrValueFormatter->Bind(fMin + ((iYLength - j) / fScale)))(), iVector2D(iYValuesWidth, 30), Font::ALIGN_RIGHT);
			WtfGraphics::Translate2D(iVector2D(0, -30));
		};
		WtfGraphics::RestoreView();
		WtfGraphics::Translate2D(iVector2D(0, -iYLength));
		WtfGraphics::SaveView();
		for(int i = 0; i < iNumXValues; i++) {
			mrFont->Value->DrawText(mvXValues[i].sLabel, iVector2D(mvXValues[i].iColumnWidth, iXValuesHeight), Font::ALIGN_CENTRE);
			WtfGraphics::Translate2D(iVector2D(mvXValues[i].iColumnWidth, 0));
		};
		WtfGraphics::RestoreView();

		// Draw data (from zero position on axes)
		DrawGraphs(fMin, fScale);
	};

	mrTexture->Deactivate();
};

String Axes::FormatValue(float f) {
	std::ostringstream ss;;

	ss << f;

	return ss.str();
};

void Axes::OnResize() {
	Update();
};

/**** Line Graph ****/

LineGraph::LineGraph(const WtfEngine::String &sXLabel, const WtfEngine::String &sYLabel)
: Axes(sXLabel, sYLabel) {
};

void LineGraph::DrawGraphs(float fOffset, float fScale) {
	for(std::list<GraphEntry>::const_iterator i = mvGraphs.begin(); i != mvGraphs.end(); i++) {
		int x = 0, c = 0;
		iVector2D vPt, vLastPt;

		WtfGraphics::SetColour(i->vColour);
		
		for(std::vector<float>::const_iterator j = i->mvYValues.begin(); j != i->mvYValues.end(); j++) {
			x += mvXValues[c].iColumnWidth / 2;
			vPt = iVector2D(x, int((*j - fOffset) * fScale));

			WtfGraphics::Draw2DBox(vPt - iVector2D(2, 2), vPt + iVector2D(2, 2));
			if(c > 0) {
				WtfGraphics::Draw2DLine(vPt, vLastPt, 2);
			};

			vLastPt = vPt;
			x += mvXValues[c].iColumnWidth / 2;
			c++;
		};
	};
};
